#include "net.h"
#include "types.h"
#include "cs8900.h"

IPaddr_t	NetOurSubnetMask=0;		/* Our subnet mask (0=unknown)	*/
volatile uchar *NetRxPkt = NULL;		/* Current receive packet		*/
volatile uchar *NetTxPacket = 0;	/* THE transmit packet			*/
volatile uchar	PktBuf[(PKTBUFSRX+1) * PKTSIZE_ALIGN + PKTALIGN];
volatile uchar *NetRxPackets[PKTBUFSRX]; /* Receive packets			*/

uchar NetBcastAddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
uchar NetEtherNullAddr[6] =	{ 0, 0, 0, 0, 0, 0 };
IPaddr_t NetOurIP;	

uchar		NetOurEther[6];		/* Our ethernet address			*/
int		NetRxPktLen;		/* Current rx packet length		*/
unsigned	NetIPID;		/* IP packet ID				*/
int		NetState;		/* Network loop state			*/

static rxhand_f *packetHandler;		/* Current RX packet handler		*/
static thand_f *timeHandler;		/* Current timeout handler		*/




unsigned long simple_strtoul(char **p)
{
	unsigned long ret = 0;

	while(**p) {
		if (**p < '0' || **p > '9')
			break;
		ret *= 10;
		ret += (**p - '0');
		++*p;
	}
	
	return (ret);
}

unsigned long string_to_ip(const char *s)
{
	unsigned long addr = 0;
	char *e;
	int i;

	if (s == NULL)
		return(0);

	e = (char *)s;

	for (i=0; i<4; ++i) {
		unsigned long val = simple_strtoul(&e);
		++e;
		addr <<= 8;
		addr |= (val & 0xFF);
	}

	return (htonl(addr));
}

static int net_check_prereq (proto_t protocol)
{
	switch (protocol) {
            /* Fall through */
        case PING:
            if (NetPingIP == 0) {
                printk ("*** ERROR: ping address not given\n");
                return (1);
            }
            goto common;
        case TFTP:
            if (NetServerIP == 0) {
                printk ("*** ERROR: `serverip' not set\n");
                return (1);
            }
    common:
            if (NetOurIP == 0) {
                printk ("*** ERROR: `ipaddr' not set\n");
                return (1);
            }
                /* Fall through */
            if (memcmp (NetOurEther, "\0\0\0\0\0\0", 6) == 0) {
                printk ("*** ERROR: `ethaddr' not set\n");
                return (1);
            }
                /* Fall through */
        default:
            return (0);
	}
	return (0);		/* OK */
}

void
NetSetHandler(rxhand_f * f)
{
	packetHandler = f;
}


int
NetCksumOk(uchar * ptr, int len)
{
	return !((NetCksum(ptr, len) + 1) & 0xfffe);
}


unsigned
NetCksum(uchar * ptr, int len)
{
	ulong	xsum;
	ushort *p = (ushort *)ptr;

	xsum = 0;
	while (len-- > 0)
		xsum += *p++;
	xsum = (xsum & 0xffff) + (xsum >> 16);
	xsum = (xsum & 0xffff) + (xsum >> 16);
	return (xsum & 0xffff);
}

int
NetEthHdrSize(void)
{
	return (ETHER_HDR_SIZE);
}


int 
NetSetEther(volatile uchar * xet, uchar * addr, uint prot)
{
	Ethernet_t *et = (Ethernet_t *)xet;

	memcpy (et->et_dest, addr, 6);
	memcpy (et->et_src, NetOurEther, 6);
	et->et_protlen = htons(prot);
	return ETHER_HDR_SIZE;
}

void
NetSetIP(volatile uchar * xip, IPaddr_t dest, int dport, int sport, int len)
{
	IP_t *ip = (IP_t *)xip;

        /*
         *	If the data is an odd number of bytes, zero the
         *	byte after the last byte so that the checksum
         *	will work.
         */
	if (len & 1)
		xip[IP_HDR_SIZE + len] = 0;

        /*
         *	Construct an IP and UDP header.
         *	(need to set no fragment bit - XXX)
         */
	ip->ip_hl_v  = 0x45;		/* IP_HDR_SIZE / 4 (not including UDP) */
	ip->ip_tos   = 0;
	ip->ip_len   = htons(IP_HDR_SIZE + len);
	ip->ip_id    = htons(NetIPID++);
	ip->ip_off   = htons(0x4000);	/* No fragmentation */
	ip->ip_ttl   = 255;
	ip->ip_p     = 17;		/* UDP */
	ip->ip_sum   = 0;
	NetCopyIP((void*)&ip->ip_src, &NetOurIP); /* already in network byte order */
	NetCopyIP((void*)&ip->ip_dst, &dest);	   /* - "" - */
	ip->udp_src  = htons(sport);
	ip->udp_dst  = htons(dport);
	ip->udp_len  = htons(8 + len);
	ip->udp_xsum = 0;

    ip->ip_sum   = ~NetCksum((uchar *)ip, IP_HDR_SIZE_NO_UDP / 2);
}



void NetReceive(volatile uchar * inpkt, int len)
{
	Ethernet_t *et;
	IP_t	*ip;
	ARP_t	*arp;
	IPaddr_t tmp;
	short	x;
	uchar *pkt;

#ifdef ET_DEBUG
	printk("packet received\n");
#endif

	NetRxPkt = inpkt;
	NetRxPktLen = len;
	et = (Ethernet_t *)inpkt;

		/* too small packet? */
	if (len < ETHER_HDR_SIZE)
		return;

	x = ntohs(et->et_protlen);


#ifdef ET_DEBUG
	printk("packet received\n");
#endif

	if (x < 1514) {
			/*
			 *	Got a 802 packet.  Check the other protocol field.
			 */
		x = ntohs(et->et_prot);

		ip = (IP_t *)(inpkt + E802_HDR_SIZE);
		len -= E802_HDR_SIZE;

	} else {
		ip = (IP_t *)(inpkt + ETHER_HDR_SIZE);
		len -= ETHER_HDR_SIZE;

	}

#ifdef ET_DEBUG
	printk("Receive from protocol 0x%x\n", x);
#endif
	switch (x) {
		case PROT_ARP:
				/*
				 * We have to deal with two types of ARP packets:
				 * - REQUEST packets will be answered by sending  our
				 *   IP address - if we know it.
				 * - REPLY packates are expected only after we asked
				 *   for the TFTP server's or the gateway's ethernet
				 *   address; so if we receive such a packet, we set
				 *   the server ethernet address
				 */
#ifdef ET_DEBUG
			puts ("Got ARP\n");
#endif
			arp = (ARP_t *)ip;
			if (len < ARP_HDR_SIZE) {
				printk("bad length %d < %d\n", len, ARP_HDR_SIZE);
				return;
			}
			if (ntohs(arp->ar_hrd) != ARP_ETHER) {
				return;
			}
			if (ntohs(arp->ar_pro) != PROT_IP) {
				return;
			}
			if (arp->ar_hln != 6) {
				return;
			}
			if (arp->ar_pln != 4) {
				return;
			}

			if (NetOurIP == 0) {
				return;
			}

			if (NetReadIP(&arp->ar_data[16]) != NetOurIP) {
				return;
			}

			switch (ntohs(arp->ar_op)) {
				case ARPOP_REQUEST:		/* reply with our IP address	*/
#ifdef ET_DEBUG
					puts ("Got ARP REQUEST, return our IP\n");
#endif
					pkt = (uchar *)et;
					pkt += NetSetEther(pkt, et->et_src, PROT_ARP);
					arp->ar_op = htons(ARPOP_REPLY);
					memcpy   (&arp->ar_data[10], &arp->ar_data[0], 6);
					NetCopyIP(&arp->ar_data[16], &arp->ar_data[6]);
					memcpy   (&arp->ar_data[ 0], NetOurEther, 6);
					NetCopyIP(&arp->ar_data[ 6], &NetOurIP);
					(void) eth_send((uchar *)et, (pkt - (uchar *)et) + ARP_HDR_SIZE);
					return;

				case ARPOP_REPLY:		/* arp reply */
						/* are we waiting for a reply */
					if (!NetArpWaitPacketIP || !NetArpWaitPacketMAC)
						break;

#ifdef ET_DEBUG
					printk("Got ARP REPLY, set server/gtwy eth addr (%02x:%02x:%02x:%02x:%02x:%02x)\n",
						arp->ar_data[0], arp->ar_data[1],
						arp->ar_data[2], arp->ar_data[3],
						arp->ar_data[4], arp->ar_data[5]);
#endif

					tmp = NetReadIP(&arp->ar_data[6]);

						/* matched waiting packet's address */
					if (tmp == NetArpWaitReplyIP) {
#ifdef ET_DEBUG
						puts ("Got it\n");
#endif
							/* save address for later use */
						memcpy(NetArpWaitPacketMAC, &arp->ar_data[0], 6);

#ifdef CONFIG_NETCONSOLE
						(*packetHandler)(0,0,0,0);
#endif
							/* modify header, and transmit it */
						memcpy(((Ethernet_t *)NetArpWaitTxPacket)->et_dest, NetArpWaitPacketMAC, 6);
						(void) eth_send(NetArpWaitTxPacket, NetArpWaitTxPacketSize);

							/* no arp request pending now */
						NetArpWaitPacketIP = 0;
						NetArpWaitTxPacketSize = 0;
						NetArpWaitPacketMAC = NULL;

					}
					return;
				default:
#ifdef ET_DEBUG
					printf("Unexpected ARP opcode 0x%x\n", ntohs(arp->ar_op));
#endif
					return;
			}
			break;

		case PROT_IP:
#ifdef ET_DEBUG
			puts ("Got IP\n");
#endif
			if (len < IP_HDR_SIZE) {
				printk("len bad %d < %d\n", len, IP_HDR_SIZE);
				return;
			}
			if (len < ntohs(ip->ip_len)) {
				printk("len bad %d < %d\n", len, ntohs(ip->ip_len));
				return;
			}
			len = ntohs(ip->ip_len);
#ifdef ET_DEBUG
			printk("len=%d, v=%02x\n", len, ip->ip_hl_v & 0xff);
#endif
			if ((ip->ip_hl_v & 0xf0) != 0x40) {
				return;
			}
			if (ip->ip_off & htons(0x1fff)) { /* Can't deal w/ fragments */
				return;
			}
			if (!NetCksumOk((uchar *)ip, IP_HDR_SIZE_NO_UDP / 2)) {
				printk("checksum bad\n");
				return;
			}
			tmp = NetReadIP(&ip->ip_dst);
			if (NetOurIP && tmp != NetOurIP && tmp != 0xFFFFFFFF) {
				return;
			}
				/*
				 * watch for ICMP host redirects
				 *
				 * There is no real handler code (yet). We just watch
				 * for ICMP host redirect messages. In case anybody
				 * sees these messages: please contact me
				 * (wd@denx.de), or - even better - send me the
				 * necessary fixes :-)
				 *
				 * Note: in all cases where I have seen this so far
				 * it was a problem with the router configuration,
				 * for instance when a router was configured in the
				 * BOOTP reply, but the TFTP server was on the same
				 * subnet. So this is probably a warning that your
				 * configuration might be wrong. But I'm not really
				 * sure if there aren't any other situations.
				 */
			if (ip->ip_p == IPPROTO_ICMP) {
				ICMP_t *icmph = (ICMP_t *)&(ip->udp_src);

				switch (icmph->type) {
					case ICMP_ECHO_REPLY:
							/*
							 *	IP header OK.  Pass the packet to the current handler.
							 */
							/* XXX point to ip packet */
						(*packetHandler)((uchar *)ip, 0, 0, 0);
						return;
					default:
						return;
				}
			} else if (ip->ip_p != IPPROTO_UDP) {	/* Only UDP packets */
				return;
			}

#ifdef CONFIG_UDP_CHECKSUM
			if (ip->udp_xsum != 0) {
				ulong   xsum;
				ushort *sumptr;
				ushort  sumlen;

				xsum  = ip->ip_p;
				xsum += (ntohs(ip->udp_len));
				xsum += (ntohl(ip->ip_src) >> 16) & 0x0000ffff;
				xsum += (ntohl(ip->ip_src) >>  0) & 0x0000ffff;
				xsum += (ntohl(ip->ip_dst) >> 16) & 0x0000ffff;
				xsum += (ntohl(ip->ip_dst) >>  0) & 0x0000ffff;

				sumlen = ntohs(ip->udp_len);
				sumptr = (ushort *) &(ip->udp_src);

				while (sumlen > 1) {
					ushort sumdata;

					sumdata = *sumptr++;
					xsum += ntohs(sumdata);
					sumlen -= 2;
				}
				if (sumlen > 0) {
					ushort sumdata;

					sumdata = *(unsigned char *) sumptr;
					sumdata = (sumdata << 8) & 0xff00;
					xsum += sumdata;
				}
				while ((xsum >> 16) != 0) {
					xsum = (xsum & 0x0000ffff) + ((xsum >> 16) & 0x0000ffff);
				}
				if ((xsum != 0x00000000) && (xsum != 0x0000ffff)) {
					printf(" UDP wrong checksum %08x %08x\n", xsum, ntohs(ip->udp_xsum));
					return;
				}
			}
#endif

				/*
				 *	IP header OK.  Pass the packet to the current handler.
				 */
			(*packetHandler)((uchar *)ip +IP_HDR_SIZE,
				ntohs(ip->udp_dst),
				ntohs(ip->udp_src),
				ntohs(ip->udp_len) - 8);
			break;
	}
}

static bd_t gbd =
{
	bi_ip_addr : 0,
	bi_ip_mask : 0,
	bi_enetaddr : {0x00, 0x0c, 0x29, 0xd1, 0x75, 0x03},
};

int NetLoop(proto_t protocol)
{
	bd_t *bd = &gbd;

	if (bd->bi_ip_addr == 0) {
		bd->bi_ip_addr = string_to_ip("192.168.1.2");
		bd->bi_ip_mask = string_to_ip("255.255.255.0");		
	}

		/* XXX problem with bss workaround */
	NetArpWaitPacketMAC = NULL;
	NetArpWaitTxPacket = NULL;
	NetArpWaitPacketIP = 0;
	NetArpWaitReplyIP = 0;
	NetArpWaitTxPacket = NULL;
	NetTxPacket = NULL;

	if (!NetTxPacket) {
		int	i;
			/*
			 *	Setup packet buffers, aligned correctly.
			 */
		NetTxPacket = &PktBuf[0] + (PKTALIGN - 1);
		NetTxPacket -= (ulong)NetTxPacket % PKTALIGN;
		for (i = 0; i < PKTBUFSRX; i++) {
			NetRxPackets[i] = NetTxPacket + (i+1)*PKTSIZE_ALIGN;
		}

    }

	if (!NetArpWaitTxPacket) {
		NetArpWaitTxPacket = &NetArpWaitPacketBuf[0] + (PKTALIGN - 1);
		NetArpWaitTxPacket -= (ulong)NetArpWaitTxPacket % PKTALIGN;
		NetArpWaitTxPacketSize = 0;
    }

	eth_halt();
	if (eth_init(bd) < 0) {
		eth_halt();
		return(-1);
	}

 restart:
	memcpy (NetOurEther, bd->bi_enetaddr, 6);

	NetState = NETLOOP_CONTINUE;

		/*
		 *	Start the ball rolling with the given start function.  From
		 *	here on, this code is a state machine driven by received
		 *	packets and timer events.
		 */

	switch (protocol) {
		case PING:
		case TFTP:
			NetCopyIP(&NetOurIP, &bd->bi_ip_addr);
			NetCopyIP(&NetOurSubnetMask, &bd->bi_ip_mask);			

			switch (protocol) {
				case TFTP:
#if 0
					NetServerIP = getenv_IPaddr ("serverip");
#endif
					break;
				case PING:
						/* nothing */
					break;
				default:
					break;
			}

			break;
		default:
			break;
	}

	switch (net_check_prereq (protocol)) {
		case 1:
				/* network not configured */
			eth_halt();
			return (-1);
		case 0:
			switch (protocol) {
				case TFTP:
						/* always use ARP to get server ethernet address */
					TftpStart();
					break;
				case PING:
                    PingStart();
					break;
				default:
					break;
			}

			NetBootFileXferSize = 0;
			break;
	}
		/*
		 *	Main packet reception loop.  Loop receiving packets until
		 *	someone sets `NetState' to a state that terminates.
		 */
	for (;;) {
#if 0
		WATCHDOG_RESET();
#endif
			/*
			 *	Check the ethernet for a new packet.  The ethernet
			 *	receive routine will process it.
			 */
        eth_rx();

			/*
			 *	Abort if ctrl-c was pressed.
			 */
#if 0
		if (ctrlc()) {
			eth_halt();
			puts ("\nAbort\n");
			return (-1);
		}
		ArpTimeoutCheck();

			/*
			 *	Check for a timeout, and run the timeout handler
			 *	if we have one.
			 */
		if (timeHandler && ((get_timer(0) - timeStart) > timeDelta)) {
			thand_f *x;
			x = timeHandler;
			timeHandler = (thand_f *)0;
			(*x)();
		}
#endif

		switch (NetState) {

			case NETLOOP_RESTART:
				goto restart;

			case NETLOOP_SUCCESS:
				if (NetBootFileXferSize > 0) {
					char buf[10];
					printk("Bytes transferred = %ld (%lx hex)\n",
						NetBootFileXferSize,
						NetBootFileXferSize);
#if 0
					sprintk(buf, "%lx", NetBootFileXferSize);
					setenv("filesize", buf);
					sprintk(buf, "%lX", (unsigned long)load_addr);
					setenv("fileaddr", buf);
#endif
				}
				eth_halt();
				return NetBootFileXferSize;

			case NETLOOP_FAIL:
				return (-1);
		}
	}
}

short htons(short s)
{
	short ret;

    ret = ((s & 0xff) << 8) + ((s & 0xff00) >> 8);
	return (ret);
}

long htonl(long l)
{
	long ret;
    ret = ((l & 0xff) << 24) + ((l & 0xff00) << 8) +
        ((l & 0xff0000) >> 8) + ((l & 0xff000000) >> 24);
    return (ret);
}


inline IPaddr_t NetReadIP(void *from)
{
	IPaddr_t ip;
	memcpy((void*)&ip, from, sizeof(ip));
	return ip;
}

/* return ulong *in network byteorder* */
inline ulong NetReadLong(ulong *from)
{
	ulong l;
	memcpy((void*)&l, (void*)from, sizeof(l));
	return l;
}

/* write IP *in network byteorder* */
inline void NetWriteIP(void *to, IPaddr_t ip)
{
	memcpy(to, (void*)&ip, sizeof(ip));
}

/* copy IP */
inline void NetCopyIP(void *to, void *from)
{
	memcpy(to, from, sizeof(IPaddr_t));
}

/* copy ulong */
inline void NetCopyLong(ulong *to, ulong *from)
{
	memcpy((void*)to, (void*)from, sizeof(ulong));
}
